package org.mozhu.mboot.common.web.controller;

import lombok.extern.slf4j.Slf4j;
import org.mozhu.mboot.backend.AjaxResponse;
import org.mozhu.mboot.common.data.IBaseService;
import org.mozhu.mboot.common.data.entity.IEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.ui.Model;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;

@Slf4j
public abstract class BaseCRUDController<M extends IEntity<PK>, PK extends Serializable> {

    protected static final String OPERATION_SUCCESS = "op.success";
    protected static final String CREATE_SUCCESS = "op.create.success";
    protected static final String UPDATE_SUCCESS = "op.update.success";
    protected static final String DELETE_SUCCESS = "op.delete.success";

    protected static final String OPERATION_FAIL = "op.fail";
    protected static final String CREATE_FAIL = "op.create.fail";
    protected static final String UPDATE_FAIL = "op.update.fail";
    protected static final String DELETE_FAIL = "op.delete.fail";


    protected IBaseService<M, PK> baseService;

    protected String entityName;

    /**
     * 实体类型
     */
    protected final Class<M> entityClass;

    protected BaseCRUDController() {
        this.entityClass = findParameterizedType(getClass(), 0);
        this.entityName = StringUtils.uncapitalize(this.entityClass.getSimpleName());
    }

    public static <T> Class<T> findParameterizedType(Class<?> clazz, int index) {
        Type parameterizedType = clazz.getGenericSuperclass();
        // CGLUB subclass target object(泛型在父类上)
        if (!(parameterizedType instanceof ParameterizedType)) {
            parameterizedType = clazz.getSuperclass().getGenericSuperclass();
        }
        if (!(parameterizedType instanceof ParameterizedType)) {
            return null;
        }
        Type[] actualTypeArguments = ((ParameterizedType) parameterizedType).getActualTypeArguments();
        if (actualTypeArguments == null || actualTypeArguments.length == 0) {
            return null;
        }
        return (Class<T>) actualTypeArguments[0];
    }

    /**
     * 共享的验证规则 验证失败返回true
     *
     * @param m
     * @param result
     * @return
     */
    protected boolean hasError(M m, BindingResult result) {
        Assert.notNull(m, "m should not be null");
        return result.hasErrors();
    }

    @RequestMapping(value = "list", method = RequestMethod.POST)
    public Object listPage(@PageableDefault(value = 10, sort = {"id"}, direction = Sort.Direction.DESC) Pageable pageable) {
        return AjaxResponse.successWithData(baseService.findAll(pageable));
    }

    // -- ajax create -- //
    @RequestMapping(value = "create", method = RequestMethod.POST)
    @ResponseBody
    public Object create(HttpServletRequest request, Model model, @Valid @ModelAttribute("m") M m, BindingResult result) {
        if (hasError(m, result)) {
            return AjaxResponse.fail(CREATE_FAIL);
        }
        try {
            beforeCreate(request, model, m, result);
            baseService.save(m);
            afterCreate(request, model, m, result);
            return AjaxResponse.success(CREATE_SUCCESS, m);
        } catch (RuntimeException e) {
            log.error("create fail", e);
            return AjaxResponse.fail(CREATE_FAIL);
        }
    }

    // -- ajax update -- //
    @RequestMapping(value = "{id}/update", method = RequestMethod.POST)
    @ResponseBody
    public Object update(HttpServletRequest request, Model model, @Valid @ModelAttribute("m") M m, BindingResult result) {
        if (hasError(m, result)) {
            return AjaxResponse.fail(UPDATE_FAIL);
        }
        try {
            beforeUpdate(request, model, m, result);
            baseService.save(m);
            afterUpdate(request, model, m, result);
            return AjaxResponse.success(UPDATE_SUCCESS, m);
        } catch (RuntimeException e) {
            log.error("update fail", e);
            return AjaxResponse.fail(UPDATE_FAIL);
        }
    }

    // -- ajax delete -- //
    @RequestMapping(value = "{id}/delete", method = {RequestMethod.GET, RequestMethod.POST})
    @ResponseBody
    public Object delete(HttpServletRequest request, @PathVariable("id") PK id) {
        try {
            M m = baseService.findById(id);
            beforeDelete(request, m);
            baseService.delete(m);
            afterDelete(request, m);
            return AjaxResponse.success(DELETE_SUCCESS);
        } catch (RuntimeException e) {
            log.error("delete fail", e);
            return AjaxResponse.fail(DELETE_FAIL);
        }
    }

    // -- ajax batchDelete -- //
    @RequestMapping(value = "batch/delete", method = {RequestMethod.GET, RequestMethod.POST})
    @ResponseBody
    public Object deleteInBatch(HttpServletRequest request, @RequestParam(value = "ids", required = false) PK[] ids) {
        try {
            List<M> all = baseService.findAllById(Arrays.asList(ids));
            for (M m : all) {
                beforeDelete(request, m);
            }
            baseService.deleteAll(all);
            for (M m : all) {
                afterDelete(request, m);
            }
            return AjaxResponse.success(DELETE_SUCCESS);
        } catch (RuntimeException e) {
            log.error("batch delete fail", e);
            return AjaxResponse.fail(DELETE_FAIL);
        }
    }

    protected void afterCreate(HttpServletRequest request, Model model, M m, BindingResult result) {
        // let someone to override
    }

    protected void beforeCreate(HttpServletRequest request, Model model, M m, BindingResult result) {
        // let someone to override
    }

    protected void afterUpdate(HttpServletRequest request, Model model, M m, BindingResult result) {
        // let someone to override
    }

    protected void beforeUpdate(HttpServletRequest request, Model model, M m, BindingResult result) {
        // let someone to override
    }

    protected void afterDelete(HttpServletRequest request, M m) {
        // let someone to implement
    }

    protected void beforeDelete(HttpServletRequest request, M m) {
        // let someone to implement
    }

    /**
     * 所有RequestMapping方法调用前的Model准备方法, 实现Struts2
     * Preparable二次部分绑定的效果,先根据form的id从数据库查出User对象,再把Form提交的内容绑定到该对象上。
     * 因为仅update()方法的form中有id属性，因此仅在update时实际执行.
     */
    @ModelAttribute
    public void getModel(@RequestParam(value = "id", required = false) PK id, Model model) {
        if (id != null) {
            model.addAttribute("m", baseService.findById(id));
        }
    }

    @Autowired
    public void setBaseService(IBaseService<M, PK> baseService) {
        this.baseService = baseService;
    }

}